package com.mijie.homi.search.service.index;

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import net.sf.json.JSONArray;

import org.apache.log4j.Logger;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.index.MultiReader;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.similarities.Similarity;
import org.apache.lucene.store.NIOFSDirectory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.Version;

import com.mijie.homi.common.domain.SearchResult;
import com.mijie.homi.search.util.ServerHelper;

public abstract class AbstractIndexService {

	private final Object resetRamIndexLock = new Object();
	private static IndexWriter ramIndexWriter = null;
	private DirectoryReader ramIndexReader = null;
	private ReadWriteLock ramIndexLock = new ReentrantReadWriteLock();
	private Set<Object> ramIndexObjectSet = new HashSet<Object>();
	private Map<String,DirectoryReader> indexReaderMap = new HashMap<String,DirectoryReader>();
	
	private  Logger logger = Logger.getLogger(getClass());
	
	protected static Version version = Version.LUCENE_43;
	
	public AbstractIndexService(){
		IndexWriterConfig config = new IndexWriterConfig(version, getAnalyzer());
		config.setOpenMode(OpenMode.CREATE);
		try {
			ramIndexWriter  = new IndexWriter(new RAMDirectory(),config);
		} catch (IOException e) { 
			logger.error(e.getMessage(), e);
		}
		logger.info("index service constructed done.");
	}
	
	
	public  abstract Analyzer getAnalyzer();
	
	public abstract String getIndexDir();
	
	protected abstract void reIndexBefore();
	
	protected abstract void reIndexDone();
	
	public final void reIndex() {
		reIndexBefore();
		try {
			clearRAMIndex();
			index();
			reIndexDone();
			List<String> list = ServerHelper.deleteRedundantIndexFile(getIndexDir());
			for(String idxf : list) indexReaderMap.remove(idxf);
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}finally{
			logger.info("reIndex at "+getIndexDir());
		}
	}
	
	protected final boolean inRAMIndexSet(Object obj){
		return ramIndexObjectSet.contains(obj);
	}
	
	protected final void addToRAMIndexSet(Object obj){
		ramIndexObjectSet.add(obj);
	}
	
	private final  void clearRAMIndex() throws IOException{
		synchronized(resetRamIndexLock){
			Lock lock = ramIndexLock.writeLock();
			try{
				lock.lock();
				ramIndexObjectSet.clear();
				ramIndexWriter.deleteAll();
				ramIndexWriter.commit();
				if(ramIndexReader!=null){
					ramIndexReader.close();
					ramIndexReader = null;
				}
			}finally{
				lock.unlock();
			}
		}
	}
	
	protected final void indexDocToRAM(Document doc){
		synchronized(resetRamIndexLock){
			Lock lock = ramIndexLock.writeLock();
			try{
				lock.lock();
				ramIndexWriter.addDocument(doc);
				ramIndexWriter.commit();
				if(ramIndexReader!=null) ramIndexReader.close();
				ramIndexReader = DirectoryReader.open(ramIndexWriter,false);
			}catch(Exception e){
				logger.error(e.getMessage(), e);
			}finally{
				lock.unlock();
			}
		}
	}
	
	protected abstract void index() throws Exception;
	
	protected final void indexDone(File dir) throws IOException {
		new File(dir,"done.txt").createNewFile();
	}
	
	public final SearchResult search(String idxf,int offset,int limit,Query query,Sort sort) throws Exception{
		return search(null,idxf,offset,limit,query,sort);
	}
	
	public final SearchResult search(Similarity similarity,String idxf,int offset,int limit,Query query,Sort sort) throws Exception{
		SearchResult result = new SearchResult();
		result.setResult(new JSONArray());
		File indexFile = null;
		if(idxf==null || idxf.trim().isEmpty() || idxf.trim().equalsIgnoreCase("null")){
			indexFile = ServerHelper.getCurrentIndexFile(getIndexDir());
		}else{
			indexFile = new File(getIndexDir()+"/"+idxf); 
		}
		if(indexFile==null || !indexFile.exists()){
			logger.info("indexFile[idxf="+idxf+""+(indexFile==null?"":indexFile.getAbsolutePath())+"] not found!!!");
			result.setCode(SearchResult.INDEX_EXPIRE);
			return result;
		}
		
		
		IndexSearcher searcher = null;
		TopDocs docs = null;
		int size = offset+limit;
		
		Lock lockRamIndexReader = ramIndexLock.readLock();
		try{
			lockRamIndexReader.lock();
			IndexReader indexReader = getIndexReader(indexFile);
			if(indexReader!=null){
				if(ramIndexReader!=null){
					MultiReader reader =  new MultiReader(indexReader,ramIndexReader);
					searcher = new IndexSearcher(reader);
				}else{
					searcher = new IndexSearcher(indexReader);
				}
			}else {
				return result;
			}
			
			if(similarity!=null) searcher.setSimilarity(similarity);
			
			if(sort!=null) docs = searcher.search(query, size,sort);
			else docs = searcher.search(query, size);
			
			if(docs!=null){
				ScoreDoc[] buff = docs.scoreDocs;
				
				if(buff.length>0){
					int remainder = buff.length - offset;
					if(remainder>0){
						if(limit>remainder) limit = remainder;
						Document doc = null;
						ScoreDoc[] dest = new ScoreDoc[limit];
						System.arraycopy(buff, offset, dest, 0, limit);
						Object item = null;
						for(ScoreDoc sd : dest){
							doc = searcher.doc(sd.doc);
							item = makeResultItem(indexFile.getName(),sd.score,doc);
							if(item!=null)
								result.getResult().add(item);
						}
					}
				}
				logger.info("search success,result size:"+docs.totalHits+"idxf:"+idxf+",query:"+query.toString());
			}
		}finally{
			lockRamIndexReader.unlock();
		}
		
		return result;
	}
	
	protected abstract Object makeResultItem(String idxf,float score,Document doc);
	
	private synchronized DirectoryReader getIndexReader(File indexFile) throws IOException{
		DirectoryReader indexReader = indexReaderMap.get(indexFile.getName());
		if(indexReader==null){
			indexReader = DirectoryReader.open(new NIOFSDirectory(indexFile));
			indexReaderMap.put(indexFile.getName(), indexReader);
		}
		return indexReader;
	}
	
	
	
	public  String getToken(String str) {
		StringReader reader = new StringReader(str); 
		TokenStream ts = null;
	    StringBuilder sb = new StringBuilder();
		try {
			ts = getAnalyzer().tokenStream(str, reader);
			ts.reset();
			CharTermAttribute termAtt = ts.getAttribute(CharTermAttribute.class);
			while (ts.incrementToken()){
				if(termAtt.length()>=2)
					sb.append(termAtt.toString()+" ");
			 }
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("@@@holy,something wrong while getToken:"+str,e);
		}finally{
			if(ts!=null){
				try {
					ts.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return sb.toString();
	}
	
	
}
